﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using MicroContainer;
using PostSharp.CodeModel;
using PostSharp.CodeModel.Helpers;
using PostSharp.CodeWeaver;
using PostSharp.Collections;
using PostSharp.Extensibility;
using PostSharp.Extensibility.Tasks;
using PostSharp.ModuleWriter;
using System.Collections;
using MicroContainer.CompileTime.Generators;

namespace MicroContainer.CompileTime
{
   /// <summary>
   /// Rejstruje transformacje związane z zastosowaniem kontenera.
   /// </summary>
   public sealed class AdviceProviderTask : Task, IAdviceProvider
   {      
      public void ProvideAdvices(Weaver codeWeaver)
      {
         try
         {
            CustomAttributeDictionaryTask customAttributeDictionaryTask = CustomAttributeDictionaryTask.GetTask(Project);
            IEnumerator<ICustomAttributeInstance> customAttributeEnumerator =
               customAttributeDictionaryTask.GetCustomAttributesEnumerator(typeof (ContainerBuilderAttribute), false);

            while (customAttributeEnumerator.MoveNext())
            {
               ContainerBuilderAttribute attribute = (ContainerBuilderAttribute)CustomAttributeHelper.ConstructRuntimeObject(customAttributeEnumerator.Current.Value, Project.Module);

               MethodDefDeclaration method = (MethodDefDeclaration) customAttributeEnumerator.Current.TargetElement;
               TypeDefDeclaration wovenType = method.DeclaringType;

               MethodBase invokableMethod = method.GetSystemMethod(null, null, BindingOptions.Default);
               ContainerBuilder builder = new ContainerBuilder();
               invokableMethod.Invoke(null, new object[] {builder});
               TypeDefDeclaration containerType = CreateContainer(wovenType);
               CodeGenerationContext ctx = new CodeGenerationContext(containerType, (MethodDefDeclaration) customAttributeEnumerator.Current.TargetElement, builder.GetMappings());               

               new DelegatesCacheGenerator().Generate(ctx);
               foreach (TypeMappingDef mappingDef in ctx.TypeMappings.Values)
               {
                  new BuildMethodGenerator(mappingDef).Generate(ctx);
               }
               foreach (TypeMappingDef mappingDef in ctx.TypeMappings.Values)
               {
                  new BuildMethodCodeGenerator(mappingDef).Generate(ctx);
               }
               new DelegatesCacheInitializerGenerator().Generate(ctx);
               foreach (TypeMappingDef mappingDef in ctx.TypeMappings.Values)
               {
                  new DelegatesCacheInitializerCodeGenerator(mappingDef).Generate(ctx);
               }
               if (attribute.Singleton)
               {
                  new StaticContainerGenerator().Generate(ctx);
               }
               new DelegatesCacheInitializerFinalizer().Generate(ctx);
               codeWeaver.AddMethodLevelAdvice(new RewriteCreateContainerAdvice(containerType, attribute.Singleton), new Singleton<MethodDefDeclaration>(method), JoinPointKinds.BeforeMethodBody, null);               
               FinishContainerConstructor(containerType);
            }
            //codeWeaver.AddTypeLevelAdvice(new ContainerBuilderCleanerAdvice(), JoinPointKinds.BeforeMethodBody, new Singleton<TypeDefDeclaration>(Project.Module.FindType(typeof(IContainerBuilder), BindingOptions.Default).GetTypeDefinition()));
         }
         catch (FatalErrorException ex)
         {
            MicroContainerMessageSource.Instance.Write(SeverityType.Fatal, ex.Message, ex.FormatArguments);
         }
      }

      private void FinishContainerConstructor(TypeDefDeclaration containerType)
      {
         MethodDefDeclaration ctor = containerType.Methods.GetOneByName(".ctor");         
         InstructionBlock block = ctor.MethodBody.RootInstructionBlock;         
         InstructionWriter writer = new InstructionWriter();
         InstructionSequence seq = ctor.MethodBody.CreateInstructionSequence();
         ctor.MethodBody.MaxStack = MethodBodyDeclaration.RecomputeMaxStack;
         block.AddInstructionSequence(seq, NodePosition.After, null);
         writer.AttachInstructionSequence(seq);         
         writer.EmitInstruction(OpCodeNumber.Ret);
         writer.DetachInstructionSequence();
      }

      private TypeDefDeclaration CreateContainer(TypeDefDeclaration outerType)
      {         
         TypeDefDeclaration containerType = new TypeDefDeclaration();
         outerType.Types.Add(containerType);
         containerType.Name = "Container";
         containerType.Attributes = TypeAttributes.Class | TypeAttributes.NestedPrivate | TypeAttributes.Sealed;         
         containerType.BaseType = (IType)Project.Module.FindType(typeof (BaseContainer), BindingOptions.Default);
         MethodDefDeclaration ctor = new MethodDefDeclaration();
         containerType.Methods.Add(ctor);
         ctor.Name = ".ctor";
         ctor.Attributes = MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.SpecialName |
                           MethodAttributes.RTSpecialName;
         ctor.ReturnParameter = new ParameterDeclaration();
         ctor.ReturnParameter.Attributes = ParameterAttributes.Retval;
         ctor.ReturnParameter.ParameterType = containerType.Module.FindType(typeof(void), BindingOptions.Default);
         ctor.CallingConvention = CallingConvention.Default;

         ctor.MethodBody = new MethodBodyDeclaration();
         ctor.MethodBody.RootInstructionBlock = ctor.MethodBody.CreateInstructionBlock();

         InstructionBlock block = ctor.MethodBody.RootInstructionBlock;
         InstructionWriter writer = new InstructionWriter();
         InstructionSequence seq = ctor.MethodBody.CreateInstructionSequence();
         block.AddInstructionSequence(seq, NodePosition.Before, null);
         writer.AttachInstructionSequence(seq);
         writer.EmitInstruction(OpCodeNumber.Ldarg_0);
         writer.EmitInstructionMethod(OpCodeNumber.Call, block.Module.FindType(typeof(BaseContainer), BindingOptions.Default).GetTypeDefinition().Methods.GetOneByName(".ctor").Translate(block.Module));
         writer.DetachInstructionSequence();

         return containerType;
      }
   }
}
